這篇開始會進入實作「標籤輸入」的系列,我已經建立一個基礎的程式架構,簡單來說新增兩個 UIViewController,也就是兩個介面,第一個介面是之後我們顯示「帳」的列表,第二個介面是我們的主角「快速記帳」。
請參考 GitHub。
基於這個程式架構,我們接下來會一一實作「標籤輸入」的程式。
首先我們先實作稍後會用到的兩種 UICollectionViewCell。
先簡單的在裡面放一個 UILabel,我們之後會一起調整內容顯示的邏輯。
class TagCollectionViewCell: UICollectionViewCell {
lazy var label: UILabel = {
return UILabel()
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(label: label)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addSubview(label: UILabel) {
contentView.addSubview(label)
let margin = contentView.layoutMarginsGuide
label.translatesAutoresizingMaskIntoConstraints = false
label.leftAnchor.constraint(equalTo: margin.leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: margin.rightAnchor).isActive = true
label.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
}
}
一樣先在裡面放一個 UITextField,之後再一起調整排版的邏輯。
須注意的是,我們要監控 UITextField 按下 Return 的事件,代表使用者輸入完標籤後,確定要建立該標籤。
因此,我們需要先定義一個 Delegate Protocol:
protocol TagTextFieldDelegate {
func didAdd(tag: String)
}
接著實作 TagTextFieldCollectionViewCell:
class TagTextFieldCollectionViewCell: UICollectionViewCell {
lazy var textField: UITextField = {
var textField = UITextField()
textField.delegate = self
return textField
}()
var delegate: TagTextFieldDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(textField: textField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addSubview(textField: UITextField) {
contentView.addSubview(textField)
let margin = contentView.layoutMarginsGuide
textField.translatesAutoresizingMaskIntoConstraints = false
textField.leftAnchor.constraint(equalTo: margin.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: margin.rightAnchor).isActive = true
textField.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
textField.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
}
}
只要 UITextField 內有文字,並按下 Return 鍵後,我們就把目前 UITextField 內的文字取出來,當做標籤丟給 TagTextFieldDelegate,如下:
extension TagTextFieldCollectionViewCell: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let text = textField.text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
return false
}
delegate?.didAdd(tag: text.trimmingCharacters(in: .whitespacesAndNewlines))
textField.text = ""
return true
}
}
稍後我們就可以在 UIViewController 利用這個 Delegate,取得使用者建立的標籤。
首先我們先在介面上加入 UICollectionView,給個大概的高度,先填上背景色方便我們辨識,並註冊前面實作的兩種 UICollectionViewCell:
class QuickCreateViewController: UIViewController {
let tagCollectionView: UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
var tags: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = MMColor.white
addSubview(tagCollectionView: tagCollectionView)
}
private func addSubview(tagCollectionView: UICollectionView) {
view.addSubview(tagCollectionView)
tagCollectionView.translatesAutoresizingMaskIntoConstraints = false
tagCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tagCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tagCollectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor).isActive = true
tagCollectionView.heightAnchor.constraint(equalToConstant: 44 * 3).isActive = true
tagCollectionView.backgroundColor = MMColor.black
tagCollectionView.dataSource = self
tagCollectionView.delegate = self
tagCollectionView.register(TagCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(TagCollectionViewCell.self))
tagCollectionView.register(TagTextFieldCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(TagTextFieldCollectionViewCell.self))
}
}
並且先簡單的加上顯示 UICollectionViewCell 的邏輯:
如下:
extension QuickCreateViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tags.count + 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == tags.count {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(TagTextFieldCollectionViewCell.self), for: indexPath) as? TagTextFieldCollectionViewCell else {
fatalError()
}
cell.backgroundColor = MMColor.red
cell.delegate = self
return cell
} else {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(TagCollectionViewCell.self), for: indexPath) as? TagCollectionViewCell else {
fatalError()
}
cell.backgroundColor = MMColor.white
cell.label.text = tags[indexPath.row]
return cell
}
}
}
接著加上 TagTextFieldDelegate 的實作,一但使用者在輸入框內按下 Return,我們取得標籤後,加入到陣列裡,並請 UICollectionView 重新讀取資料:
extension QuickCreateViewController: TagTextFieldDelegate {
func didAdd(tag: String) {
tags.append(tag)
tagCollectionView.reloadData()
}
}
最後,我們需要監控 UICollectionView 重新顯示 Cell 的事件,並在 UICollectionView 重新顯示資料時,再把 TagTextFieldCollectionViewCell 裡面的 UITextField 重新設為 First Responder,否則每次使用者新增完一個 Tag,並重新顯示 UICollectionView 時,鍵盤都會隱藏起來,就不能快速地連續新增標籤。
extension QuickCreateViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
(cell as? TagTextFieldCollectionViewCell)?.textField.becomeFirstResponder()
}
}
至此,完整程式碼請看 GitHub。
下一篇我們將針對 UICollectionView 調整排版。
備註:有些地方用了 lazy var,但其實沒必要,之後預計會有一篇重構,到時候再來處理這些誤用。
做個實驗一下
似乎 force refresh 頁面個幾次之後就會有高亮了
class TagCollectionViewCell: UICollectionViewCell {
lazy var label: UILabel = {
return UILabel()
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(label: label)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addSubview(label: UILabel) {
contentView.addSubview(label)
let margin = contentView.layoutMarginsGuide
label.translatesAutoresizingMaskIntoConstraints = false
label.leftAnchor.constraint(equalTo: margin.leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: margin.rightAnchor).isActive = true
label.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
}
}
我強制重整還是沒有欸